#include <stdio.h>
#include <stdlib.h>
#include "CasilleroIDA.h"
#include "CasilleroA.h"
#include "CNodo.h"
#include "Excepcion.h"
#include "ID_A.h"
#include "HManhattan.h"
#include "HPDB.h"
#include "Aestrella.h"
#include <stack.h>

enum ALGORITMOS {
    IDA,
    AStar
};

enum HEURISTICAS {
    PDB,
    Manhattan,
};

CNodo *resolver(const int *tablero, short posVacio, int tam, ALGORITMOS alg, HEURISTICAS heu);
void imprimirResultado(CNodo *goal, ALGORITMOS alg, HEURISTICAS heu, int caso, bool impresion);

//Abstracciones:
static const char conj3[] 
     = {0, 0, 0,
        0, 0, 1,
        1, 1, 1};
static const char conj4[] 
     = {0, 0, 0, 0,
        0, 0, 1, 1,
        1, 1, 1, 2,
        2, 2, 2, 2};

static const char conj5[] 
     = {0, 0, 0, 0, 0,
        0, 0, 1, 1, 1,
        1, 1, 1, 2, 2,
        2, 2, 2, 2, 3,
        3, 3, 3, 3, 3};

/**
 * Carga un archivo de prueba y resuelve los n-puzzle que contenga.
 * @param argv - 
 * El parametro 1 debe tener la ruta del archivo.
 * El parametro 2 debe contener el tamaño (ancho ó algo) de los tableros en el archivo.
 * El parametro 3 contiene el numero maximo de tableros a estudiar.
 * El parametro 4 es 1 si se desea que se imprima la ruta recorrida por los
 * algoritmos, y 0 si se desea que solo se imprima el numero de pasos.
 */
int main(int argc, char** argv) {
    int casos, c, tam;
    int tablero[25];
    bool impresion;
    
    printf("Corriendo |0:%s |1:%s |2:%s |3:%s |4:%s\n\n", argv[0], argv[1], argv[2], argv[3], argv[4]);
    
    FILE *f = fopen(argv[1], "r");
    if(!f){
        char err[512];
        sprintf("Archivo no encontrado \"%s\"\n", argv[1]);
        perror(err);
        return EXIT_FAILURE;
    }
    printf("Archivo abierto: \"%s\"\n", argv[1]);
    
    tam   = atoi(argv[2]);
    casos = atoi(argv[3]);
    impresion = (bool) atoi(argv[4]);
    
    for (c = 0; c < casos; c++) {
        int i;
        short posVacio;

        //Leyendo tablero:
        for (i = 0; i < tam * tam; i++){
            if(! fscanf(f, "%d", &(tablero[i])) ) return EXIT_SUCCESS;
            if(tablero[i] == 0) posVacio = (short) i;
        }
        
        try {
            //imprimirResultado(resolver(tablero, posVacio, tam, IDA, PDB), IDA, PDB, c, impresion);
            //imprimirResultado(resolver(tablero, posVacio, tam, IDA, Manhattan), IDA, Manhattan, c, impresion);
            //imprimirResultado(resolver(tablero, posVacio, tam, AStar, PDB), AStar, PDB, c, impresion);
            imprimirResultado(resolver(tablero, posVacio, tam, AStar, Manhattan), AStar, Manhattan, c, impresion);
        } catch(Excepcion *e) {
            printf("Excepcion sin atrapar:\n%s\n", e->Descripcion());
            delete e;
        }
        printf("\n\n");
        
    }
    if(!fclose(f))
        printf("Archivo de entrada cerrado exitosamente");
    return EXIT_SUCCESS;
}


CNodo *resolver(const int *tablero, short posVacio, int tam, ALGORITMOS alg, HEURISTICAS heu){
    
    static HPDB *pdb;
    
    Heuristica *heuristica;
    Algoritmo *algoritmo;
    
    Casillero *c;
    CNodo *s0;
    
    switch(heu){
        case PDB:
            printf("Resolviendo con PDB ");
            const char *conj;
            switch(tam){
                case 3: conj = conj3; break;
                case 4: conj = conj4; break;
                case 5: conj = conj5; break;
            }
            if(pdb == NULL) {
                pdb = new HPDB(conj, tam);
                printf("PDB Calculada. ");
                fflush(stdout);
            }
            heuristica = pdb;
            break;
        case Manhattan:
            printf("Resolviendo con Manhattan ");
            heuristica = new HManhattan();
            break;
    }
    
    switch(alg){
        case AStar:
            printf("y AStar\n");
            c = new CasilleroA(tablero, tam, posVacio);
            algoritmo = new Aestrella(200);
            break;
        case IDA:
            printf("y IDA\n");
            c = new CasilleroIDA(tablero, tam, posVacio);
            algoritmo = new ID_A();
            break;
    }
    
    s0 = new CNodo(c, (CNodo::Operador) - 1, NULL);

    CNodo *r = algoritmo->buscar(s0, heuristica);
    
    if(heuristica != pdb)
        delete heuristica;
    delete algoritmo;
    
    return r;
}

void imprimirResultado(CNodo *goal, ALGORITMOS alg, HEURISTICAS heu, int caso, bool impresion){
    
    static const char *heustr[] = {"PDB", "Manhattan"};
    static const char *algstr[] = {"A*", "ID_A*"};
    
    printf("Caso %d:\nAlgoritmo ", caso);
    if(alg==AStar)      printf("%s", algstr[0]);
    else                printf("%s", algstr[1]);
    printf(" con la Heuristica ");
    if(heu==PDB)        printf("%s", heustr[0]);
    else                printf("%s", heustr[1]);
    
    if(impresion) printf("\n");
    
    stack<CNodo *> pila;  
    
    while (goal != NULL) {
        pila.push(goal);
        goal = goal->Enlace();
    }
    
    int i = 0;
    
    while(!pila.empty()){
        CNodo *r = pila.top();
        
        if(impresion) {
            char str[256];
            
            if(i > 0) {
                char *dir;
                switch (r->OperadorEnlace()) {
                    case CNodo::abajo: dir = "abajo";
                        break;
                    case CNodo::arriba: dir = "arriba";
                        break;
                    case CNodo::derecha: dir = "derecha";
                        break;
                    case CNodo::izquierda: dir = "izquierda";
                        break;
                    default: dir = "-";
                }

                printf("\nOperacion %d: %s\n", i, dir);
            }
            
            r->Casillas()->toString(str);
            printf("\n%s\n", str);
        }
        
        
        i++;
        pila.pop();
    }
    
    printf(" tomo %d pasos\n", i-1);
}
